#include <cstdlib>
#include <fstream>
#include <cassert>
#include <iostream>
#include <sstream>
#include <utility>
#include <algorithm>

#include "datamap.hpp"

using namespace std;

// Creates a new DataMap object for the given file type.
DataMap::DataMap(const char& delimiter, const string& missingID):
  delimiter_(delimiter),
  missingID_(missingID) {

  // Clear the data container just in case
  //colHeaderSet_.clear();
  data_.clear();

}

DataMap::~DataMap() {
  /* EMPTY DTOR */
}

// Merges a data table from a given file into the data container
void DataMap::merge(string fileName) {

  // Initialize stream to read from file
  ifstream dataStream;
  dataStream.open(fileName.c_str());
  if ( !dataStream.good() ) {
    cerr << "Failed to open file '" << fileName << "' for reading" << endl;
    exit(1);
  }
    
  // Read data from the stream into the data container "data_"
  DataMap::mergeDelimitedDataFromStream(dataStream);
}

// Merges data in the specified stream into the data container "data_"
void DataMap::mergeDelimitedDataFromStream(ifstream& dataStream) {

  // Stores one field of the table
  string field;

  // Stores one row of the table
  string row;

  // Remove upper left element from the table as useless
  getline(dataStream,field,delimiter_);

  // Next read the first row, which should contain the column headers
  getline(dataStream,row);
  stringstream ss( DataMap::chomp(row) ); // chomp makes sure the end-of-line characters will be removed

  // Read all column headers from the data stream
  //map<string,map<string,string> >::const_iterator firstRowIt( data_.begin() );
  vector<string> newColHeaders;
  while ( getline(ss,field,delimiter_) ) {
    
    // Store all column headers into newColHeaders
    newColHeaders.push_back(field);
    colKeys_.insert(field);
    
    // If the column header does not yet exist in the map (and, thus, in the colHeaders_ vector), we'll add it
    //if ( data_.size() > 0 ) {
    //  if ( colHeaderSet_.find(field) == colHeaderSet_.end() ) {
    //	colHeaderSet_.insert(field);
    //	colHeaders_.push_back(field);
    // }
    //} else {
    // colHeaderSet_.insert(field);
    // colHeaders_.push_back(field);
    //}
    
  }

  // We should have reached the end of stream. NOTE: failbit is set since the last element read did not end at '\t'
  assert( ss.eof() );
  //assert( !ss.fail() );

  //cout << "DataMap::readData -- TODO: make sure no failbits etc are set!" << endl;

  // The number of column headers in the new data set
  size_t nNewCols = newColHeaders.size();

  // Go through the rest of the rows in the new data set
  while ( getline(dataStream,row) ) {

    // Remove all existing end-of-line characters
    row = DataMap::chomp(row);

    // Read row from the stream
    ss.clear();
    ss.str("");

    // Read the string back to a stream
    ss << row;

    // Read the next row header from the stream
    getline(ss,field,delimiter_);
    
    //if ( data_.find(field) == data_.end() ) {
    rowKeys_.insert(field);
    //}

    // Populate the map with new data
    for ( size_t i = 0; i < nNewCols; ++i ) {

      getline(ss,data_[ rowKeys_[field] ][ colKeys_[ newColHeaders[i] ] ],delimiter_);

    }

    // Ensure the row of data was read completely and no error flags were raised
    assert(!ss.fail());
    assert(ss.eof());
  }

}

string DataMap::chomp(const string& str) {

  // Chop at the first newline character, if it exists
  int crIdx = str.find("\r");
  int lfIdx = str.find("\n");
  int terminatorIdx = crIdx;
  if (lfIdx != -1 && lfIdx < crIdx) {
    terminatorIdx = lfIdx;
  }

  string ret(str);

  return( terminatorIdx != -1 ? ret.substr(0,terminatorIdx) : ret );

}

string DataMap::toUpperCase(const string& str) {
  int (*pf)(int) = toupper;
  string strcopy(str);
  transform(strcopy.begin(), strcopy.end(), strcopy.begin(), pf);
  return(strcopy);
}

void DataMap::printToStream(ostream& toStream) {
  
  //toStream << "\t";

  for ( size_t j = 0; j < colKeys_.size(); ++j ) {
    toStream << "\t" << colKeys_[j];
  }

  toStream << endl;

  for ( size_t i = 0; i < rowKeys_.size(); ++i ) {
    toStream << rowKeys_[i];
    for ( size_t j = 0; j < colKeys_.size(); ++j ) {

      toStream << "\t" << DataMap::at(i,j);

    }
    toStream << endl;
  }

}

string DataMap::at(const size_t i, const size_t j) {
  
  if ( data_.find(i) == data_.end() ) {
    cerr << "DataMap is corrupted!" << endl;
    exit(1);
  }

  if ( data_[i].find(j) == data_[i].end() ) {
    return( missingID_ );
  }

  return( data_[i][j] );

}
